﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.ObjectModel;

namespace ProjectEuler.Linq
{
    public static class Integers
    {
        public static IEnumerable<Int32> To(this Int32 from, Int32 to)
        {
            if (from == to)
            {
                yield return from;
            }
            else if (from < to)
            {
                while (from <= to)
                {
                    yield return from++;
                }
            }
            else if (from > to)
            {
                while (from >= to)
                {
                    yield return from--;
                }
            }
        }

        public static IEnumerable<Int64> To(this Int64 from, Int64 to)
        {
            if (from == to)
            {
                yield return from;
            }
            else if (from < to)
            {
                while (from <= to)
                {
                    yield return from++;
                }
            }
            else if (from > to)
            {
                while (from >= to)
                {
                    yield return from--;
                }
            }
        }

        public static IEnumerable<Int64> To(this Int64 from, Int64 to, Int64 increment)
        {
            if (from == to)
            {
                yield return from;
            }
            else if (from < to)
            {
                if (increment < 1)
                {
                    throw new ArgumentOutOfRangeException("increment");
                }

                do
                {
                    yield return from;
                    from += increment;
                }
                while (from <= to);
            }
            else if (from > to)
            {
                if (increment > -1)
                {
                    throw new ArgumentOutOfRangeException("increment");
                }

                do
                {
                    yield return from;
                    from += increment;
                }
                while (from >= to);
            }
        }

        public static IEnumerable<Int32> From(Int32 first)
        {
            while (first < Int32.MaxValue)
            {
                yield return first++;
            }
        }
        
        public static IEnumerable<Int64> From(Int64 first)
        {
            while (first < Int64.MaxValue)
            {
                yield return first++;
            }
        }

        public static IEnumerable<Int64> Fibonacci()
        {
            return Fibonacci(0, 1);
        }

        public static IEnumerable<Int64> Fibonacci(Int64 first, Int64 second)
        {
            var prev = first;
            var current = second;

            yield return prev;

            do
            {
                yield return current;

                var next = checked(prev + current);

                prev = current;
                current = next;
            }
            while (Int64.MaxValue - prev > current);
        }

        public static IEnumerable<UInt64> Primes()
        {
            checked
            {
                foreach (var p in PrimesUnder500)
                {
                    yield return (UInt64)(p);
                }

                var increment = 2ul;
                var candidate = 503ul;
                var primes = new List<UInt64>(PrimesUnder500.Select(s => (UInt64)(s)));

                do
                {
                    var index = 0;
                    var divisor = primes[index];

                    var prime = true;

                    while ((divisor * divisor) <= candidate)
                    {
                        if ((candidate % divisor) == 0)
                        {
                            prime = false;
                            break;
                        }

                        index++;
                        divisor = primes[index];
                    }

                    if (prime)
                    {
                        primes.Add(candidate);
                        yield return candidate;
                    }

                    candidate += increment;

                    if (increment == 2)
                    {
                        increment = 4;
                    }
                    else
                    {
                        increment = 2;
                    }
                }
                while ((candidate + 4) < UInt64.MaxValue);
            }
        }

        public static IEnumerable<Int64> NDigitNumbers(Int32 n)
        {
            return NDigitNumbers(n, n);
        }

        public static IEnumerable<Int64> NDigitNumbers(Int32 minDigits, Int32 maxDigits)
        {
            var min = (Int64)(Math.Pow(10, (minDigits - 1)));
            var max = (Int64)(Math.Pow(10, maxDigits) - 1);
            
            return (min).To(max);
        }

        public static IEnumerable<Int64> TriangleNumbers()
        {
            return TriangleNumbers(1);
        }

        public static IEnumerable<Int64> TriangleNumbers(Int64 first)
        {
            if (!first.IsTriangular())
            {
                throw new ArgumentException("Not a triangular number.", "first");
            }

            var tn = first;
            var n = first.TriangularRoot().Value;

            while (tn > 0)
            {
                yield return tn = (n++).Triangular();
            }
        }

        public static IEnumerable<Int64> PandigitalNumbers()
        {
            return from c in (2).To(9)
                   from p in PandigitalNumbers(c)
                   select p;
        }
        
        public static IEnumerable<Int64> PandigitalNumbers(Int32 n)
        {
            var digits = (1).To(n).ToArray();

            return from permutation in digits.Permutations()
                   select Int64FromDecimalDigits(permutation);
        }

        private static Int64 Int64FromDecimalDigits(IEnumerable<Int32> digits)
        {
            var result = 0L;

            foreach (var d in digits)
            {
                result *= 10;
                result += d;
            }

            return result;
        }

        public static readonly ICollection<Int32> PrimesUnder500 = new ReadOnlyCollection<Int32>(new[]
        {
              2,   3,   5,   7,  11,  13,  17,  19,
             23,  29,  31,  37,  41,  43,  47,  53,
             59,  61,  67,  71,  73,  79,  83,  89,
             97, 101, 103, 107, 109, 113, 127, 131,
            137, 139, 149, 151, 157, 163, 167, 173,
            179, 181, 191, 193, 197, 199, 211, 223,
            227, 229, 233, 239, 241, 251, 257, 263,
            269, 271, 277, 281, 283, 293, 307, 311,
            313, 317, 331, 337, 347, 349, 353, 359,
            367, 373, 379, 383, 389, 397, 401, 409,
            419, 421, 431, 433, 439, 443, 449, 457,
            461, 463, 467, 479, 487, 491, 499,
        });
    }
}
